package jeff.plugin.mybatis.util;

import jeff.plugin.mybatis.exception.IORuntimeException;
import jeff.plugin.mybatis.io.FastByteArrayOutputStream;
import jeff.plugin.mybatis.lang.Charsets;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;

public class IOUtil {

    public static final int EOF = -1;

    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    // ---------------------------------------------------------------- silent close

    public static void closeQuietly(final Closeable... closeables) {
        if (closeables == null || closeables.length == 0) {
            return;
        }
        for (final Closeable closeable : closeables) {
            closeQuietly(closeable);
        }
    }

    public static void closeQuietly(final Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (final IOException ioe) {
            // ignore
        }
    }

    /**
     * 关闭<br>
     * 关闭失败不会抛出异常
     *
     * @param closeable 被关闭的对象
     */
    public static void closeQuietly(AutoCloseable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (Exception e) {
                // ignore
            }
        }
    }

    // ---------------------------------------------------------------- copy stream


    public static long copy(final InputStream input, final OutputStream output) throws IORuntimeException {
        return copy(input, output, DEFAULT_BUFFER_SIZE);
    }

    public static long copy(final InputStream input, final OutputStream output, final int bufferSize) throws IORuntimeException {
        return copy(input, output, bufferSize, null);
    }

    public static long copy(final InputStream input, final OutputStream output, int bufferSize, StreamProgress streamProgress) throws IORuntimeException{
        Assert.notNull(input, "InputStream is null !");
        Assert.notNull(output, "OutputStream is null !");
        if (bufferSize <= 0) {
            bufferSize = DEFAULT_BUFFER_SIZE;
        }

        byte[] buffer = new byte[bufferSize];
        long size = 0;
        if (null != streamProgress) {
            streamProgress.start();
        }

        int readSize;
        try{
            while (EOF != (readSize = input.read(buffer))) {
                output.write(buffer, 0, readSize);
                size += readSize;
                output.flush();
                if (null != streamProgress) {
                    streamProgress.progress(size);
                }
            }
        }
        catch (IOException e){
            throw new IORuntimeException(e);
        }

        if (null != streamProgress) {
            streamProgress.finish();
        }
        return size;
    }

    /**
     * 将Reader中的内容复制到Writer中 使用默认缓存大小
     *
     * @param reader Reader
     * @param writer Writer
     * @return 拷贝的字节数
     * @throws IORuntimeException IO异常
     */
    public static long copy(Reader reader, Writer writer) throws IORuntimeException {
        return copy(reader, writer, DEFAULT_BUFFER_SIZE);
    }

    /**
     * 将Reader中的内容复制到Writer中
     *
     * @param reader Reader
     * @param writer Writer
     * @param bufferSize 缓存大小
     * @return 传输的byte数
     * @throws IORuntimeException IO异常
     */
    public static long copy(Reader reader, Writer writer, int bufferSize) throws IORuntimeException {
        return copy(reader, writer, bufferSize, null);
    }

    /**
     * 将Reader中的内容复制到Writer中
     *
     * @param reader Reader
     * @param writer Writer
     * @param bufferSize 缓存大小
     * @param streamProgress 进度处理器
     * @return 传输的byte数
     * @throws IORuntimeException IO异常
     */
    public static long copy(Reader reader, Writer writer, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
        char[] buffer = new char[bufferSize];
        long size = 0;
        int readSize;
        if (null != streamProgress) {
            streamProgress.start();
        }
        try {
            while ((readSize = reader.read(buffer, 0, bufferSize)) != EOF) {
                writer.write(buffer, 0, readSize);
                size += readSize;
                writer.flush();
                if (null != streamProgress) {
                    streamProgress.progress(size);
                }
            }
        } catch (Exception e) {
            throw new IORuntimeException(e);
        }
        if (null != streamProgress) {
            streamProgress.finish();
        }
        return size;
    }

    /**
     * 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
     * 本方法不会关闭流
     *
     * @param in 输入流
     * @param out 输出流
     * @param bufferSize 缓存大小
     * @param streamProgress 进度条
     * @return 传输的byte数
     * @throws IORuntimeException IO异常
     */
    public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
        return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
    }

    /**
     * 拷贝文件流，使用NIO
     *
     * @param in 输入
     * @param out 输出
     * @return 拷贝的字节数
     * @throws IORuntimeException IO异常
     */
    public static long copy(FileInputStream in, FileOutputStream out) throws IORuntimeException {
        Assert.notNull(in, "FileInputStream is null!");
        Assert.notNull(out, "FileOutputStream is null!");

        final FileChannel inChannel = in.getChannel();
        final FileChannel outChannel = out.getChannel();

        try {
            return inChannel.transferTo(0, inChannel.size(), outChannel);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    /**
     * 拷贝流，使用NIO，不会关闭流
     *
     * @param in {@link ReadableByteChannel}
     * @param out {@link WritableByteChannel}
     * @param bufferSize 缓冲大小，如果小于等于0，使用默认
     * @param streamProgress {@link StreamProgress}进度处理器
     * @return 拷贝的字节数
     * @throws IORuntimeException IO异常
     */
    public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
        Assert.notNull(in, "InputStream is null !");
        Assert.notNull(out, "OutputStream is null !");

        ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
        long size = 0;
        if (null != streamProgress) {
            streamProgress.start();
        }
        try {
            while (in.read(byteBuffer) != EOF) {
                byteBuffer.flip();// 写转读
                size += out.write(byteBuffer);
                byteBuffer.clear();
                if (null != streamProgress) {
                    streamProgress.progress(size);
                }
            }
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        if (null != streamProgress) {
            streamProgress.finish();
        }

        return size;
    }

    // ---------------------------------------------------------------- copy to OutputStream

    public static <T extends OutputStream> T copy(final Reader input, final T output) {
        return copy(input, output, Charsets.UTF_8);
    }

    public static <T extends OutputStream> T copy(final Reader input, final T output, final Charset charset)throws IORuntimeException{
        try (Writer out = new OutputStreamWriter(output, charset)) {
            copy(input, out);
            return output;
        }
        catch (IOException e){
            throw new IORuntimeException(e);
        }
    }

    public static FastByteArrayOutputStream copyToOutputStream(final Reader input, final Charset charset){
        try (FastByteArrayOutputStream output = new FastByteArrayOutputStream(DEFAULT_BUFFER_SIZE)) {
            copy(input, output, charset);
            return output;
        }
    }

    public static FastByteArrayOutputStream copyToOutputStream(final InputStream input) {
        try (FastByteArrayOutputStream output = new FastByteArrayOutputStream(DEFAULT_BUFFER_SIZE)) {
            copy(input, output);
            return output;
        }
    }

    // ---------------------------------------------------------------- read bytes
    /**
     * 流中字节转换为字节数组
     * @param inputStream   输入流
     * @return  返回字节数组
     * @throws IOException  异常
     */
    public static byte[] readBytes(final InputStream inputStream){
        FastByteArrayOutputStream fastByteArrayOutputStream = copyToOutputStream(inputStream);
        return fastByteArrayOutputStream.toByteArray();
    }

    /**
     * 流中获取一定长度的字节,并返回字节数组
     * @param inputStream   输入流
     * @param length    待获取的字节长度
     * @return  一定长度的字节数组
     * @throws IOException 异常
     */
    public static byte[] readBytes(InputStream inputStream, int length){
        final ByteArrayOutputStream output = new ByteArrayOutputStream();
        copy(inputStream, output, length);
        return output.toByteArray();
    }

    public static byte[] readBytes(final Reader input) {
        return readBytes(input, Charsets.UTF_8);
    }

    public static byte[] readBytes(final Reader input, final Charset charset) {
        return copyToOutputStream(input, charset).toByteArray();
    }

    // ---------------------------------------------------------------- to stream

    /**
     * String 转为流
     *
     * @param content 内容
     * @param charsetName 编码
     * @return 字节流
     */
    public static ByteArrayInputStream toStream(String content, String charsetName) {
        return toStream(content, Charsets.toCharset(charsetName));
    }

    /**
     * String 转为流
     *
     * @param content 内容
     * @param charset 编码
     * @return 字节流
     */
    public static ByteArrayInputStream toStream(String content, Charset charset) {
        if (content == null) {
            return null;
        }
        return new ByteArrayInputStream(StringUtil.getBytes(content, charset));
    }

    /**
     * 文件转为流
     *
     * @param file 文件
     * @return {@link FileInputStream}
     */
    public static FileInputStream toStream(File file) {
        try {
            return new FileInputStream(file);
        } catch (FileNotFoundException e) {
            throw new IORuntimeException(e);
        }
    }

    /**
     * 转换为{@link BufferedInputStream}
     *
     * @param in {@link InputStream}
     * @return {@link BufferedInputStream}
     * @since 4.0.10
     */
    public static BufferedInputStream toBuffered(InputStream in) {
        return (in instanceof BufferedInputStream) ? (BufferedInputStream) in : new BufferedInputStream(in);
    }

    /**
     * 转换为{@link BufferedOutputStream}
     *
     * @param out {@link OutputStream}
     * @return {@link BufferedOutputStream}
     * @since 4.0.10
     */
    public static BufferedOutputStream toBuffered(OutputStream out) {
        return (out instanceof BufferedOutputStream) ? (BufferedOutputStream) out : new BufferedOutputStream(out);
    }

    /**
     * 将{@link InputStream}转换为支持mark标记的流<br>
     * 若原流支持mark标记，则返回原流，否则使用{@link BufferedInputStream} 包装之
     *s
     * @param in 流
     */
    public static InputStream toMarkSupportStream(InputStream in) {
        if (null == in) {
            return null;
        }
        if (!in.markSupported()) {
            return new BufferedInputStream(in);
        }
        return in;
    }

    /**
     * 转换为{@link PushbackInputStream}<br>
     * 如果传入的输入流已经是{@link PushbackInputStream}，强转返回，否则新建一个
     *
     * @param in {@link InputStream}
     * @param pushBackSize 推后的byte数
     * @return {@link PushbackInputStream}
     */
    public static PushbackInputStream toPushbackStream(InputStream in, int pushBackSize) {
        return (in instanceof PushbackInputStream) ? (PushbackInputStream) in : new PushbackInputStream(in, pushBackSize);
    }

}
